package com.gitee.wsl.flow.create

import com.gitee.wsl.concurrent.api.Retryable
import com.gitee.wsl.flow.F
import com.gitee.wsl.flow.create.datasource.DataSourceLoadState
import com.gitee.wsl.flow.create.datasource.DataState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn

@OptIn(ExperimentalCoroutinesApi::class, ExperimentalForInheritanceCoroutinesApi::class)
class RetryableDataFlow<R>(
    scope: CoroutineScope,
    sharingStarted: SharingStarted = SharingStarted.Lazily,
    initialValue: DataState<R> = DataSourceLoadState.idle<R>() ,
    private val flowBlock: () -> Flow<DataState<R>>
) : StateFlow<DataState<R>> , Retryable {
    private val retryCount = MutableStateFlow(0)

    private val data: StateFlow<DataState<R>> =
        retryCount.flatMapLatest { flowBlock() }
            .withPreviousDataStateValue()
            .stateIn(scope, sharingStarted, initialValue)

    fun retryIfError() {
        if (data.value.isErr) {
            retry()
        }
    }

    override fun retry() {
        retryCount.value += 1
    }

    override val replayCache: List<DataState<R>>
        get() = data.replayCache

    override val value: DataState<R>
        get() = data.value

    override suspend fun collect(collector: FlowCollector<DataState<R>>): Nothing {
        data.collect(collector)
    }
}

internal fun <T> Flow<DataState<T>>.withPreviousDataStateValue(): Flow<DataState<T>> = flow {
    var previous: DataState<T>? = null
    collect { current ->
        previous = current
        emit(current)
    }
}

fun <R> F.retryAbleFlow(
    scope: CoroutineScope,
    sharingStarted: SharingStarted = SharingStarted.Lazily,
    initialValue:DataState<R>,
    flowBlock: () -> Flow<DataState<R>>
) = RetryableDataFlow(scope, sharingStarted, initialValue, flowBlock)

fun <R> CoroutineScope.retryAbleFlow(
    sharingStarted: SharingStarted = SharingStarted.Lazily,
    initialValue:DataState<R>,
    flowBlock: () -> Flow<DataState<R>>
) = F.retryAbleFlow(this,sharingStarted,initialValue,flowBlock)